'''
1. Получить необходимые данные по торговым свечам, используя библиотеку tinkoff_invest
2. Сохранить данные в базу данных, модель которой описана в файле model_db.py, используя библиотеку sqlalchemy
3. Получить необходимую выборку данных из БД
4. Проводим преобразование данных - нормализацию и уменьшение размерности, получаем некий "паттерн" для определенного момента времени.
5. Смотрим, как ведет себя цена после полученного паттерна через определенные промежутки времени (3, 5, 8, 13 дней).
6. Записываем паттерн в БД вместе c результатом проверки.
7. Обучаем модель на данных
8. Проверяем качество обучения
9. Выводим результаты на экран
'''

from datetime import datetime
import sys
from typing import List, Dict, Tuple
import os
from tinkoff.invest import CandleInterval, Client
from tinkoff.invest import HistoricCandle
from tinkoff.invest.schemas import CandleSource
from tinkoff.invest.utils import now, candle_interval_to_timedelta, money_to_decimal, round_datetime_range
from model_db import HistoricCandleModel, engine
from sqlalchemy.orm import Session
from loguru import logger

# logger.add(sys.stderr, format="{time} {level} {message}", level="DEBUG")

class Pattern:

    def __init__(self, figi: str,  count_candles: int, time: datetime) -> None:
        self.figi = figi
        self.time = time
        self.list_intervals = intervals
        self.count_candles = count_candles
        self.token = token

class GetPattern:

    _list_intervals: List[CandleInterval]
    _token: str
    _pattern: List[List[HistoricCandle]]

    def __init__(self, figi: str, time: datetime, intervals: List[CandleInterval], count_candles: int, token: str) -> None:
        self.figi = figi
        self.time = time
        self.list_intervals = intervals
        self.count_candles = count_candles
        self.token = token
        self.get_pattern()


    @property
    def list_intervals(self):
        return self._list_intervals
    
    @list_intervals.setter
    def list_intervals(self, value: List[CandleInterval]):
        self._list_intervals = value

    @property
    def token(self):
        return None
    
    @token.setter
    def token(self, value: str):
        self._token = value

    def norm_candle(self, candle: HistoricCandle, interval: CandleInterval) -> HistoricCandleModel:
        result = HistoricCandleModel(
            open=float(money_to_decimal(candle.open)),
            close=float(money_to_decimal(candle.close)),
            high=float(money_to_decimal(candle.high)),
            low=float(money_to_decimal(candle.low)),
            volume=candle.volume,
            interval=interval.value,
            time=candle.time,
            figi=self.figi,
            is_complete=candle.is_complete,
            candle_source=candle.candle_source.value
        )
        logger.info(f"Преобразование свечи в модель завершено")
        return result
    
    def check_candles_in_db(self, interval: CandleInterval) -> List[HistoricCandleModel]:
        """
        Checks for the presence of candles in the database for a given instrument and interval.
        
        :param interval: Time interval for the candles.
        :return: List of candles from the database.
        """
        try:
            with Session(bind=engine) as session:
                candles = session.query(HistoricCandleModel).filter(
                    HistoricCandleModel.figi == self.figi,
                    HistoricCandleModel.interval == interval.value,
                    HistoricCandleModel.time <= self.time
                ).order_by(HistoricCandleModel.time.desc()).limit(self.count_candles).all()
                logger.info(f"Свечи из базы данных загружены в колличестве {len(candles)}, для интервала {interval}")
                return candles
        except Exception as e:
            logger.error(f"Ошибка при получении свечей из базы данных: {e}")
            return []


    def save_candles_to_db(self, candles: List[HistoricCandleModel]):
        """
        Saves a list of candles to the database.
        
        :param candles: List of candles to be saved.
        """
        try:
            with Session(bind=engine) as session:
                for candle_model in candles:
                    existing_candle = session.query(HistoricCandleModel).filter(
                        HistoricCandleModel.figi == candle_model.figi,
                        HistoricCandleModel.interval == candle_model.interval,
                        HistoricCandleModel.time == candle_model.time
                    ).first()
                    if not existing_candle:
                        session.add(candle_model)
                        logger.info(f"Свеча {candle_model.time} для инструмента {candle_model.figi} и интервала {candle_model.interval} добавлена в базу данных")
                session.commit()
        except Exception as e:
            logger.error(f"Ошибка при сохранении свечей в базу данных: {e}")


    def get_candles_from_interval(self, interval: CandleInterval) -> HistoricCandleModel:
        """
        Retrieves the required number of candles for a given instrument and interval,
        first checking the database, and if not present, requesting through the API.
        
        :param interval: Time interval for the candles.
        :return: List of candles.
        """
        candles_from_db = self.check_candles_in_db(interval)
        if len(candles_from_db) == self.count_candles:
            return candles_from_db
        else:
            candles_from_api = self.request_candles_through_api(interval)
            self.save_candles_to_db(candles_from_api)
            return candles_from_api

    
    def get_from_to(self, time: datetime, interval: CandleInterval) -> Tuple[datetime, datetime]:
        _to = time
        _from = time - candle_interval_to_timedelta(interval) * self.count_candles
        return round_datetime_range([_to, _from], interval)
    
    
    def request_candles_through_api(self, interval: CandleInterval) -> List[HistoricCandleModel]:
        """
        Requests candles through the API for a given instrument and interval.
        
        :param interval: Time interval for the candles.
        :return: List of candles from the API.
        """
        candles_list = []  # Initialize a list to store candles
        condition = 0
        _to, _from = self.get_from_to(self.time, interval)

        while len(candles_list) < self.count_candles:  # Until the required number of candles is collected
            
            with Client(self._token) as client:

                logger.info(f"Запрос по следующим параметрам figi={self.figi}, from={_from}, to={_to}, interval={CandleInterval(interval)}")
                
                try:
                    candles = client.market_data.get_candles(
                        figi=self.figi,
                        from_=_from,
                        to=_to,
                        interval=CandleInterval(interval)
                    )
                except Exception as e:
                    logger.error(f"Ошибка при запросе свечей через API: {e}")

                if not candles.candles:
                    logger.info(f"Свечи не найдены для интервала {interval}")
                    condition += 1
                    _to, _from = self.get_from_to(_from, interval)
                    logger.info(f"Нет свечей. Новые параметры запроса figi={self.figi}, from={_from}, to={_to}, interval={CandleInterval(interval)}")
                elif len(candles.candles) < self.count_candles:
                    _to, _from = self.get_from_to(_from, interval)
                    condition += 1
                    logger.info(f"Недостаточно свечей. Новые параметры запроса figi={self.figi}, from={_from}, to={_to}, interval={CandleInterval(interval)}")
                else:
                    condition = 111
                
                if condition > 100:
                    logger.info(f"Превышено количество попыток запроса свечей. Возвращаем результаты запроса.")
                    break

                candles_list.extend(self.norm_candle(candle, interval) for candle in candles.candles)

        return candles_list[:self.count_candles]
    
    def get_pattern(self) -> List[List[HistoricCandle]]:
        self._pattern = [self.get_candles_from_interval(interval) for interval in self.list_intervals]

    def get_pattern_from_db(self):
        pass

    
if __name__ == "__main__":

    # from model_db import go_db
    # go_db()

    def get_token(token_name: str):
        token = os.getenv(token_name)
        if not token:
            print("Токен не найден. Пожалуйста, добавьте токен в переменные окружения.")
            new_token = input("Введите новый токен: ")
            os.environ[token_name] = new_token
            token = new_token
        return token

    token = get_token("token_name")
    intervals = [
        CandleInterval.CANDLE_INTERVAL_15_MIN,
        CandleInterval.CANDLE_INTERVAL_HOUR,
        CandleInterval.CANDLE_INTERVAL_4_HOUR,
        CandleInterval.CANDLE_INTERVAL_DAY,
        CandleInterval.CANDLE_INTERVAL_WEEK
    ]

    date = datetime.strptime("2024-02-11 11:00:00", "%Y-%m-%d %H:%M:%S")

    patt = GetPattern(figi="BBG004731032", time=date, intervals=intervals, count_candles=5, token=token)

    l = patt._pattern
    for i in l:
        print(len(i))


